#include <QHash>
#include "RoomStructEngine.h"

inline int qHash(QVector3D q, size_t seed = 0) noexcept
{
    return qHash(q.x(),seed) + qHash(q.y(),seed) + qHash(q.z(),seed);
}

namespace RoomEngine {

template<typename T>
class Matrix3D
{
public:
    Matrix3D(int dx,int dy,int dz):dx(dx),dy(dy),dz(dz){
        t = new T[dx * dy * dz];
    }

    Matrix3D(const QVector3D& extend):Matrix3D(extend.x(),extend.y(),extend.z()){

    }

    ~Matrix3D(){
        delete [] t;
        t = nullptr;
    }

    T& operator()(int x,int y,int z){
       return  *(t + (dx * dz) * y + dx * z + x);
    }

    T& operator()(const QVector3D& pos){
        return operator()(pos.x(),pos.y(),pos.z());
    }

private:
    T *t = nullptr;
    int dx;
    int dy;
    int dz;
};

struct RoomData
{
    Room::Type type = Room::None;
};

typedef Matrix3D<RoomData> RoomsStruct;

Port::Port()
{

}

QVector3D Port::fromPos() const
{
    return m_fromPos;
}

void Port::setFromPos(const QVector3D &newFromPos)
{
    if (m_fromPos == newFromPos)
        return;
    m_fromPos = newFromPos;
    emit fromPosChanged();
}

Room *Port::fromRoom() const
{
    return m_fromRoom;
}

void Port::setFromRoom(Room *newFromRoom)
{
    if (m_fromRoom == newFromRoom)
        return;
    m_fromRoom = newFromRoom;
    emit fromRoomChanged();
}

QVector3D Port::toPos() const
{
    return m_toPos;
}

void Port::setToPos(const QVector3D &newToPos)
{
    if (m_toPos == newToPos)
        return;
    m_toPos = newToPos;
    emit toPosChanged();
}

Room *Port::toRoom() const
{
    return m_toRoom;
}

void Port::setToRoom(Room *newToRoom)
{
    if (m_toRoom == newToRoom)
        return;
    m_toRoom = newToRoom;
    emit toRoomChanged();
}

Room::Room()
{

}

QVector3D Room::position() const
{
    return m_position;
}

void Room::setPosition(const QVector3D &newPosition)
{
    if (m_position == newPosition)
        return;
    m_position = newPosition;
    emit positionChanged();
}

QList<Port *> Room::ports() const
{
    return m_ports;
}

void Room::setPorts(const QList<Port *> &newPorts)
{
    if (m_ports == newPorts)
        return;
    m_ports = newPorts;
    emit portsChanged();
}

Room::Type Room::type() const
{
    return m_type;
}

void Room::setType(const Type &newType)
{
    if (m_type == newType)
        return;
    m_type = newType;
    emit typeChanged();
}

RoomStructEngine::RoomStructEngine()
{

}

QList<Room *> RoomStructEngine::rooms() const
{
    return m_rooms;
}

void RoomStructEngine::setRooms(const QList<Room *> &newRooms)
{
    if (m_rooms == newRooms)
        return;
    m_rooms = newRooms;
    emit roomsChanged();
}

int RoomStructEngine::seed() const
{
    return m_seed;
}

void RoomStructEngine::setSeed(int newSeed)
{
    if (m_seed == newSeed)
        return;
    m_seed = newSeed;
    emit seedChanged();
}

QVector3D RoomStructEngine::extend() const
{
    return m_extend;
}

void RoomStructEngine::setExtend(const QVector3D &newExtend)
{
    if (m_extend == newExtend)
        return;
    m_extend = newExtend;
    emit extendChanged();
}

static QVector3D vectorFloor(const QVector3D& i){
    return {std::floor(i.x()),std::floor(i.y()),std::floor(i.z())};
}

void RoomStructEngine::generate()
{

    QRandomGenerator gen;
    gen.seed(m_seed);

    RoomsStruct rooms(m_extend);
    auto centerPoint = vectorFloor(m_extend / 2);
    auto startPoint = centerPoint;
    //取中心房间为开始房间
    rooms(startPoint).type = Room::Start;

    //随机选取一个位置(但不是开始房间)作为结束房间
    auto endRoom = QVector3D{};
    do
    {
        endRoom = QVector3D(gen.bounded((int)m_extend.x()),
                            gen.bounded((int)m_extend.y()),
                            gen.bounded((int)m_extend.z()));
    }while (endRoom == startPoint);


    rooms(endRoom).type = Room::End;
    auto path = generateNormalRoom(startPoint,endRoom);
    for(int i = 1;i<path.size() - 1;i++)
    {
        rooms(path[i]).type = Room::Normal;
    }

    QHash<QVector3D,Room*> newRooms;

    for(int x = 0;x < m_extend.x();x++)
    {
        for(int y = 0;y < m_extend.y();y++)
        {
            for(int z = 0;z < m_extend.z();z++)
            {
                auto pos = QVector3D(x,y,z);
                if(rooms(pos).type == Room::None)
                {
                    continue;
                }

                Room* r = new Room();
                r->setParent(this);
                r->setPosition(pos);
                r->setType(rooms(pos).type);
                newRooms[pos] = r;
            }
        }
    }

    //将相邻房间用出入口相连
    for(const auto &i:newRooms)
    {
        QList<Port*> ports;

        auto genPort = [&](const QVector3D& dir,const QVector3D& from,const QVector3D& to){
            auto otherPos = i->position() + dir;
            auto other = newRooms.value(otherPos);
            if(other)
            {
                Port* port = new Port();
                port->setParent(i);
                port->setFromPos(from);
                port->setFromRoom(i);
                port->setToPos(to);
                port->setToRoom(other);
                ports << port;
            }

        };
        genPort({1,0,0},{1,0,0.5},{0,0,0.5});
        genPort({-1,0,0},{0,0,0.5},{1,0,0.5});
        genPort({0,1,0},{0.5,1,0.5},{0.5,0,0.5});
        genPort({0,-1,0},{0.5,0,0.5},{0.5,1,0.5});
        genPort({0,0,1},{0.5,0,1},{0.5,0,0});
        genPort({0,0,-1},{0.5,0,0},{0.5,0,1});

        i->setPorts(ports);
    }

    auto oldRooms = m_rooms;
    setRooms(newRooms.values());
    for(const auto &i:oldRooms)
    {
        delete i;
    }
}

QList<QVector3D> RoomStructEngine::generateNormalRoom(QVector3D start,QVector3D end)
{
    //通过寻路算法在终点和起点之间找到连接的房间
    QList<QVector3D> ret;

    QVector3D current;
    current = start;
    ret << current;

    while (true) {
        if(current == end)
        {
            break;
        }

        if(current.x() < end.x())
        {
            current += {1,0,0};
        }else if(current.x() > end.x())
        {
            current += {-1,0,0};
        }else if(current.y() > end.y())
        {
            current += {0,1,0};
        }else if(current.y() > end.y())
        {
            current += {0,-1,0};
        }else if(current.z() > end.z())
        {
            current += {0,0,1};
        }else if(current.z() > end.z())
        {
            current += {0,0,-1};
        }
        ret << current;
    }

    return ret;
}
}
